/*****************************************************************************
 * $LastChangedDate: 2012-10-25 21:35:57 -0400 (Thu, 25 Oct 2012) $
 * @file
 * @author  Jim E. Brooks  http://www.palomino3d.org
 * @brief   Sprite class.
 *//*
 * LEGAL:   COPYRIGHT (C) 2008 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE VERSION 2 (GPL 2).
 *****************************************************************************/

#define FX_SPRITE_CC 1
#include <utility>
#include "base/module.hh"
using namespace base;
#include "math/module.hh"
#include "math/funcs_trig.hh"
using namespace math;
#include "gfx/module.hh"
#include "gfx/osg.hh"
#include "gfx/osg_funcs.hh"
using namespace gfx;
#include "object/module.hh"
using namespace object;
#include "fx/module.hh"
#include "fx/sprite.hh"

#include <osg/AutoTransform>
#include <osg/Texture2D>

namespace fx {

/*****************************************************************************
 * Macros (hi-freq).
 * The ABS macros do an absolute rotation (from zero angle).
 * The REL macros incrementally rotate from current angle.
 *****************************************************************************/
// hi-freq
#define SPRITE_INIT_VERTEXS()                                       \
{{                                                                  \
    const fp w = mWidth * 0.5f;                                     \
                                                                    \
    (*mVertexs)[0][XX] = -w;                                        \
    (*mVertexs)[0][YY] = -w;                                        \
                                                                    \
    (*mVertexs)[1][XX] =  w;                                        \
    (*mVertexs)[1][YY] = -w;                                        \
                                                                    \
    (*mVertexs)[2][XX] =  w;                                        \
    (*mVertexs)[2][YY] =  w;                                        \
                                                                    \
    (*mVertexs)[3][XX] = -w;                                        \
    (*mVertexs)[3][YY] =  w;                                        \
                                                                    \
    (*mVertexs)[0][ZZ] =                                            \
    (*mVertexs)[1][ZZ] =                                            \
    (*mVertexs)[2][ZZ] =                                            \
    (*mVertexs)[3][ZZ] = 0.0f;                                      \
}}

#define SPRITE_ROTATE_X_Y_ABS( V, S, C, X, Y )                      \
{{                                                                  \
    (V)[XX] = (X)*(C) - (Y)*(S);                                    \
    (V)[YY] = (X)*(S) + (Y)*(C);                                    \
}}

#define SPRITE_ROTATE_X_Y_REL( V, S, C )                            \
{{                                                                  \
    /* use temps */                                                 \
    const fp x = (V)[XX];                                           \
    const fp y = (V)[YY];                                           \
    (V)[XX] = x*(C) - y*(S);                                        \
    (V)[YY] = x*(S) + y*(C);                                        \
}}

#define SPRITE_ROTATE_VERTEXS_ABS( V, SINCOS )                              \
{{                                                                          \
    const fp w = mWidth * 0.5f;                                             \
    SPRITE_ROTATE_X_Y_ABS( (V)[0], SINCOS.first, SINCOS.second, -w, -w )    \
    SPRITE_ROTATE_X_Y_ABS( (V)[1], SINCOS.first, SINCOS.second,  w, -w )    \
    SPRITE_ROTATE_X_Y_ABS( (V)[2], SINCOS.first, SINCOS.second,  w,  w )    \
    SPRITE_ROTATE_X_Y_ABS( (V)[3], SINCOS.first, SINCOS.second, -w,  w )    \
}}

#define SPRITE_ROTATE_VERTEXS_REL( V, SINCOS )                      \
{{                                                                  \
    SPRITE_ROTATE_X_Y_REL( (V)[0], SINCOS.first, SINCOS.second );   \
    SPRITE_ROTATE_X_Y_REL( (V)[1], SINCOS.first, SINCOS.second );   \
    SPRITE_ROTATE_X_Y_REL( (V)[2], SINCOS.first, SINCOS.second );   \
    SPRITE_ROTATE_X_Y_REL( (V)[3], SINCOS.first, SINCOS.second );   \
}}

////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////  Sprite  ////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

const NodeSort Sprite::msSpriteNodeSort = NodeSort::Attribs( NodeSort::Attribs::TRANSPARENT );

/*****************************************************************************
 * ctor/dtor.
 *****************************************************************************/
Sprite::Sprite( const Args& args )
:   Parent(Graph::MakeMinimalGraph(),args.mPosition),
    mGeometry(new osg::Geometry),
    mVertexs(new osg::Vec3Array(4)),  // quad
    mColor0(args.mColor),
    mWidth(args.mWidth)
{
    SET_TYPESIG(this,TYPESIG_SPRITE);

    // Make an osg::AutoTransform from a drawable (Geometry).
    RefPtr<osg::AutoTransform> autotransform = new osg::AutoTransform;
  //autotransform->setPosition( args.mPosition );  // use SetPosition()
  //autotransform->setPivotPoint( osg::Vec3(0,0,0) );  // redundant
    autotransform->setAutoRotateMode( osg::AutoTransform::ROTATE_TO_SCREEN );
    RefPtr<osg::Drawable> drawable = MakeDrawable( args );
    RefPtr<osg::Geode> geode = new osg::Geode;
    geode->addDrawable( drawable.get() );
    autotransform->addChild( geode.get() );

    // Attach osg::AutoTransform to this Object's graph.
    GetGraph()->AttachNode( autotransform.get() );

    // Set position.  This affects Object::mTransformNode which AutoTransform is under,
    // which is why AutoTransform's origin is left at 0,0,0.
    SetPosition( args.mPosition );
}

Sprite::~Sprite()
{
    INVALIDATE_TYPESIG(this,TYPESIG_SPRITE);
}

/*****************************************************************************
 * Subroutine of ctor.
 *****************************************************************************/
RefPtr<osg::Drawable>
Sprite::MakeDrawable( const Args& args )
{
CHECK_TYPESIG(this,TYPESIG_SPRITE);

    // Attach stateset (defined by caller) which ideally should be shared
    // by multiple Sprites (state-sorting).
    mGeometry->setStateSet( args.mStateSet.get() );

    // Basis of SPRITE_INIT_VERTEXS().
    const fp width = mWidth * 0.5f;
    const osg::Vec3f widthHalf3(  width,  0.0f, 0.0f );
    const osg::Vec3f heightHalf3(  0.0f, width, 0.0f );

    // 4 vertexs.
    SPRITE_INIT_VERTEXS();
    mGeometry->setVertexArray( mVertexs.get() );

    // 1 normal vector.
    RefPtr<osg::Vec3Array> norms = new osg::Vec3Array( 1 );
    (*norms)[0] = widthHalf3 ^ heightHalf3;  // cross product in OSG
    (*norms)[0].normalize();
    mGeometry->setNormalArray( norms.get() );
    mGeometry->setNormalBinding( osg::Geometry::BIND_OVERALL );

    // 4 texture coordinates.
    RefPtr<osg::Vec2Array> texCoords = new osg::Vec2Array( 4 );
    (*texCoords)[0].set( 0.0f, 0.0f );
    (*texCoords)[1].set( 1.0f, 0.0f );
    (*texCoords)[2].set( 1.0f, 1.0f );
    (*texCoords)[3].set( 0.0f, 1.0f );
    mGeometry->setTexCoordArray( 0, texCoords.get() );

    // Primitive set.
    mGeometry->addPrimitiveSet( new osg::DrawArrays( osg::PrimitiveSet::QUADS, 0, 4 ) );

    // Colors.
    mGeometry->setColorBinding( osg::Geometry::BIND_OVERALL );
    SetColor0( args.mColor );

// FYI: This is too slow as it defeats state-sorting.
// Rather than repeatedly re-binding the same texture,
// the caller is supposed to pass a stateset with a texture bound to it.
// But this method still makes texture coordinates for the caller.
#if 0
    // Bind texture.
    args.mTexture->Bind( *stateSet );
#endif

    // Return as Drawable.
    return mGeometry.get();
}

/*****************************************************************************
 * Roll the Sprite (hi-freq).
 *
 * (obsolete comment):
 * Rotating the base Object's transform node doesn't work for osg::Billboard,
 * so rotate the vertexs themselves.
 *****************************************************************************/
void
Sprite::RotateRoll( const Radian radian, const bool relative )
{
CHECK_TYPESIG(this,TYPESIG_SPRITE);

    const std::pair<fp,fp> sin_cos = SinCos<fp>( radian );

    if ( relative )
    {
        SPRITE_ROTATE_VERTEXS_REL( *mVertexs, sin_cos );
    }
    else
    {
        SPRITE_ROTATE_VERTEXS_ABS( *mVertexs, sin_cos );
    }

    // Tell OSG geometry was modified.
    OsgGeometryWasModified( mGeometry, mVertexs );
}

} // namespace fx
